/**
 * @file dirfile_data.c
 *
 * @date Oct 5, 2011
 * @author seth
 *
 * @brief This file is part of EBEX State Estimator, created for the EBEX project
 *
 * This software is copyright (C) 2011 Columbia University
 *
 * EBEX State Estimator is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * EBEX State Estimator is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EBEX State Estimator; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <getdata.h>

#include <log_util.h>
#include <memory_util.h>
#include <data_file.h>
#include <dirfile_data.h>

typedef struct dirfile_datafiles_s dirfile_datafiles_t;
typedef struct dirfile_datafiles_s
{
	dirfile_datafiles_t *_next;
	DIRFILE	*fp;
	size_t refcnt;
} dirfile_datafiles_t;

static dirfile_datafiles_t *dirfile_datafiles = NULL;

static DIRFILE *dirfile_open(const char *m_dirfilename, bool m_forwriting)
{
	dirfile_datafiles_t *retval;

	for (retval = dirfile_datafiles; retval && retval->fp; retval = retval->_next)
	{
		if (! strcasecmp(m_dirfilename, gd_dirfilename(retval->fp))) break;
	}

	if (!retval || !dirfile_datafiles)
	{
		retval = e_malloc(sizeof(dirfile_datafiles_t));
		retval->fp = gd_open(m_dirfilename, m_forwriting ? GD_RDWR | GD_CREAT : GD_RDONLY);
		if (gd_error(retval->fp) != GD_E_OK)
		{
			elog(log_level_fatal, "Could not open %s for %s", m_dirfilename, m_forwriting ? "writing" : "reading");
			gd_close(retval->fp);
			e_free(retval);
			return NULL;
		}
		retval->_next = dirfile_datafiles;
		dirfile_datafiles = retval;
		retval->refcnt = 1;
	}
	else
	{
		retval->refcnt++;
	}

	return retval->fp;
}

static bool dirfile_close(DIRFILE *m_dirfile)
{
	dirfile_datafiles_t *temp_ptr1;
	dirfile_datafiles_t *temp_ptr2;

	for (temp_ptr1 = dirfile_datafiles; temp_ptr1 && temp_ptr1->fp; temp_ptr1 = temp_ptr1->_next)
	{
		if (m_dirfile == m_dirfile) break;
		temp_ptr2 = temp_ptr1;
	}

	if (!temp_ptr1)
	{
		gd_close(m_dirfile);
		return true;
	}
	else
	{
		if (--temp_ptr1->refcnt <= 0)
		{
			gd_close(m_dirfile);
			if (temp_ptr1 == dirfile_datafiles)
			{
				dirfile_datafiles = temp_ptr1->_next;
			}
			else
			{
				temp_ptr2->_next = temp_ptr1->_next;
			}
			e_free(temp_ptr1);
		}
	}

	return true;
}

void dirfile_data_close(data_file_t *m_file)
{
	elog(log_level_extreme, "Closing file %s", m_file->filename?m_file->filename:"(Empty Filename)");

	if (m_file->dirfile_fp) dirfile_close(m_file->dirfile_fp);
	e_free_quiet(m_file->filename);
	e_free_quiet(m_file->conf.dirfile.field_code);

	m_file->dirfile_fp = NULL;
	m_file->filename = NULL;
}

bool dirfile_data_open(data_file_t *m_file, const char *m_filename, size_t m_offset)
{

	if (!m_file)
	{
		elog(log_level_fatal, "Passed NULL pointer");
		return false;
	}
	if (!m_filename)
	{
		elog(log_level_fatal, "Asked to open NULL filename");
		return false;
	}

	if (!(m_file->dirfile_fp = dirfile_open(m_filename, m_file->is_sink)))
	{
		elog(log_level_fatal, "Could not open %s for %s", m_filename, m_file->is_sink ? "writing" : "reading");
		return false;
	}

	m_file->filename = e_strdup(m_filename);
	m_file->type = DATA_FILE_DIRFILE;
	m_file->stride = 1;
	m_file->offset = m_offset;
	m_file->conf.dirfile.spf = 0;
	m_file->conf.dirfile.current_frame = m_offset;
	m_file->conf.dirfile.current_sample = 0;

	m_file->num_elements = (size_t) gd_nframes64(m_file->dirfile_fp);

	elog(log_level_verbose, "File %s opened with %zu elements.  Setting start to element %zu", m_file->filename, m_file->num_elements, m_offset);

	return true;
}

bool dirfile_data_reset(data_file_t *m_file)
{

	if (!m_file)
	{
		elog(log_level_fatal, "Passed NULL pointer");
		return false;
	}

	m_file->conf.dirfile.current_frame = m_file->offset;
	m_file->conf.dirfile.current_sample = 0;

	elog(log_level_verbose, "File %s reset to starting frame %zu", m_file->filename, m_file->offset);

	return true;
}

/**
 * Fills the data databuffer with packed data (e.g. DIRFILES).  The databuffer will be filled from the current position
 * in the open file and placed in the databuffer at position 
 * @param m_source Pointer to the data source
 * @param m_buffer Offset buffer to receive the data
 * @param m_numelements Number of doubles to insert into the buffer
 * @return Number of elements actually inserted
 */
size_t dirfile_data_fillbuffer(data_file_t *m_source, double *m_buffer, const size_t m_numelements)
{
	size_t element_to_read = m_numelements;
	size_t elements_read = 0;


	if (m_source->stride == 1)
	{
		elements_read = gd_getdata(m_source->dirfile_fp, m_source->conf.dirfile.field_code,
				m_source->conf.dirfile.current_frame, m_source->conf.dirfile.current_sample,
				m_numelements / m_source->conf.dirfile.spf, m_numelements % m_source->conf.dirfile.spf,
				GD_FLOAT64, m_buffer);
		m_source->conf.dirfile.current_frame += ((m_source->conf.dirfile.current_sample + elements_read) / m_source->conf.dirfile.spf);
		m_source->conf.dirfile.current_sample = ((m_source->conf.dirfile.current_sample + elements_read) % m_source->conf.dirfile.spf);
	}
	else
	{
		while (element_to_read--)
		{
			if (! gd_getdata(m_source->dirfile_fp, m_source->conf.dirfile.field_code,
					m_source->conf.dirfile.current_frame, m_source->conf.dirfile.current_sample,
					0, 1,
					GD_FLOAT64, m_buffer + elements_read))
			{
				break;
			}
			elements_read++;
			m_source->conf.dirfile.current_sample++;
			if (m_source->conf.dirfile.current_sample >= (off_t)m_source->conf.dirfile.spf)
			{
				m_source->conf.dirfile.current_sample = 0;
				m_source->conf.dirfile.current_frame++;
			}

		};
	}

	return elements_read;
}

size_t dirfile_data_writebuffer(data_file_t *m_source, double *m_buffer, const size_t m_numelements)
{
	size_t retval;
	elog(log_level_extreme, "Writing %zu elements to %s", m_numelements, m_source->filename?m_source->filename:"(UNK)");
	retval = gd_putdata(m_source->dirfile_fp, m_source->conf.dirfile.field_code,
			m_source->conf.dirfile.current_frame, m_source->conf.dirfile.current_sample,
			m_numelements / m_source->conf.dirfile.spf, m_numelements % m_source->conf.dirfile.spf,
			GD_FLOAT64, m_buffer);

	m_source->conf.dirfile.current_frame += ((m_source->conf.dirfile.current_sample + retval) / m_source->conf.dirfile.spf);
	m_source->conf.dirfile.current_sample = ((m_source->conf.dirfile.current_sample + retval) % m_source->conf.dirfile.spf);

	return retval;
}

bool dirfile_data_set_parameters(data_file_t *m_source, const void *m_param )
{
	dirfile_data_param_t *param = (dirfile_data_param_t*)m_param;

	m_source->stride = param->stride;

	m_source->conf.dirfile.spf = gd_spf(m_source->dirfile_fp, param->field);
	if (m_source->conf.dirfile.spf)
	{
		m_source->conf.dirfile.field_code = e_strdup(param->field);
		return true;
	}
	else if (m_source->is_sink)
	{
		if (!gd_add_raw(m_source->dirfile_fp, param->field, GD_FLOAT64, param->spf, 0))
		{
			m_source->conf.dirfile.spf = param->spf;
			m_source->conf.dirfile.field_code = e_strdup(param->field);
			gd_metaflush(m_source->dirfile_fp);
			return true;
		}
		elog(log_level_warning, "Could not add %s to dirfile %s", param->field, m_source->filename);
	}
	else
	{
		elog(log_level_warning, "Could not open %s from dirfile %s", param->field, m_source->filename);
	}

	return false;
}
